/* 
Copyright (c) 2004,2005, 2006, 2007, 2008 University of Illinois 

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
AccessibilityExtension.ns(function() { with (AccessibilityExtension.LIB) {

	var AE = AccessibilityExtension.ns;  
	
	const tablenav_up = 1;
	const tablenav_down = 2;
	const tablenav_left = 3;
	const tablenav_right = 4;

	function dataTableDisplayDetails() {}
	dataTableDisplayDetails.prototype = {
		dataElementNames : ['icita07', 'icita08','order', 'baseuri','dispTitle'],
		columnHeaders : ['column.header.icita07.label', 'column.header.icita08.label','column.header.order.label','column.header.baseuri.label','column.header.title.label'],
		
		accessIssuesDataElementNames : ['missingsummary', 'missingheaders', 'duplicatesummary', 'thmissingID', 'duplicateID', 'tdmissingheaders', 'invalidIDREF'],
		accessIssuesAttrNames : ['', '', 'summary', '', '', '', ''],
		accessIssuesMessages : ['dataTables.msg.missingsummary', 'dataTables.msg.missingheaders', 'dataTables.msg.duplicatesummary', 'dataTables.msg.thmissingID', 'dataTables.msg.duplicateID', 'dataTables.msg.tdmissingheaders', 'dataTables.msg.invalidIDREF'],
		
		accessIssuesUseObjectFunctions : true,
	}
	var dataTableDisplayDetails = new dataTableDisplayDetails();
	
	function dataTable(node, order) {
		this.parseNode(node);
		this.setdispTitle();
		this.order = order;
	}
	dataTable.prototype = {
		icita07 : "",
		icita08 : "",
		baseuri : "",
		headers : Array(),
		caption : "",
		summary : "",
		table   : Array(),
		title	: "",
		dispTitle : "",
		tableNode : null,
		node : null,
		order	: false,
		height : -1,
		width : -1,
		mode : false,
		activePos : Array(),
		missingsummary : false,
		missingheaders : false,
		duplicatesummary : false,
		thmissingID : false,
		duplicateID : false,
		tdmissingheaders : false,
		invalidIDREF : null,  //null=NA, <value>=the invalid reference
		display : true,
		displayDetails: dataTableDisplayDetails,
		populateWindow : function(windoc){
			var summary = windoc.getElementById("frame-summary");
			if (summary) {
				summary.value = this.summary;
			}
			
			this.activePos = new Array(0,0);
		},
		setdispTitle : function() {
			if (this.tableNode.title && this.tableNode.title != "") {
				this.dispTitle = cleanSpaces(this.tableNode.title);
			} else if (this.tableNode.caption && this.tableNode.caption.textContent 
				&& this.tableNode.caption.textContent != "") {
				this.dispTitle = getDisplayableTextRecursively(this.tableNode.caption);
			} else {
				this.dispTitle = accessext_getBundleString('dataTables.tableDefaultTitle',(this.order+1).toString());
			}		
		},
		getTreeNode : function(windoc) {
			return accesxt_generateTreeboxResults(windoc,this);
	/* 		SMF 3/25/08 - The standard function - not including the item.setAttribute -
	 * 		seems to work fine.
	 		var item, child;
			var row = windoc.createElement("treerow");
			
			child = windoc.createElement("treecell");
			child.setAttribute("label", (this.icita07).toString());
			row.appendChild(child);
			
			child = windoc.createElement("treecell");
			child.setAttribute("label", (this.icita08).toString());
			row.appendChild(child);
			
			child = windoc.createElement("treecell");
			child.setAttribute("label", (this.order).toString());
			row.appendChild(child);
			
			child = windoc.createElement("treecell");
			child.setAttribute("label", (this.baseuri).toString());
			row.appendChild(child);
			
			child = windoc.createElement("treecell");
			child.setAttribute("label", this.dispTitle);
			row.appendChild(child);
					
			item = windoc.createElement("treeitem");
			item.setAttribute("value",this.order.toString());
			item.appendChild(row);*/
			
			return item; 
		},
		setRowColor : function (item) {
		  var row = item.getElementsByTagName('treerow');
	 	  if (currStandard() == 'icita08') {
		    if (this.missingheaders || this.duplicatesummary || this.thmissingID || this.duplicateID || this.tdmissingheaders || this.invalidIDREF != null) {
				row[0].setAttribute('properties','failRow');
			}
		  }	
		},
		getNoteText : function() {
			try {
				var note = '';
				var tmp = '';
				var currStd = currStandard();
				var notApplicable = accessext_getBundleSingleString('msg.unenforced');
				if (this.missingsummary) {
					var severity = getErrMsgSeverity('dataTables.msg.missingsummary', currStd);
					if (severity != notApplicable) {note += severity + ': ' + accessext_getBundleSingleString('dataTables.msg.missingsummary') + ' \n'; }
				}
				if (this.missingheaders) {
					var severity = getErrMsgSeverity('dataTables.msg.missingheaders', currStd);
					if (severity != notApplicable) {note += severity + ': ' + accessext_getBundleSingleString('dataTables.msg.missingheaders') + ' \n'; }
				}
				if (this.duplicatesummary) {
					var severity = getErrMsgSeverity('dataTables.msg.duplicatesummary', currStd);
					if (severity != notApplicable) {note += severity + ': ' + accessext_getBundleSingleString('dataTables.msg.duplicatesummary') + ' \n'; }
				}
				if (this.thmissingID) {
					var severity = getErrMsgSeverity('dataTables.msg.thmissingID', currStd);
					if (severity != notApplicable) {note += severity + ': ' + accessext_getBundleSingleString('dataTables.msg.thmissingID') + ' \n'; }
				}
				if (this.duplicateID) {
					var severity = getErrMsgSeverity('dataTables.msg.duplicateID', currStd);
					if (severity != notApplicable) {note += severity + ': ' + accessext_getBundleSingleString('dataTables.msg.duplicateID') + ' \n'; }
				}
				if (this.tdmissingheaders) {
					var severity = getErrMsgSeverity('dataTables.msg.tdmissingheaders', currStd);
					if (severity != notApplicable) {note += severity + ': ' + accessext_getBundleSingleString('dataTables.msg.tdmissingheaders') + ' \n'; }
				}
				if (this.invalidIDREF != null) {
					var severity = getErrMsgSeverity('dataTables.msg.invalidIDREF', currStd);
					if (severity != notApplicable) {
						tmp = accessext_getBundleSingleString('dataTables.msg.invalidIDREF');
						tmp = tmp.replace('%1', this.invalidIDREF);
						note += severity + ': ' + tmp + ' \n'; 
					}
				}
			} catch (ex) {
				accessext_consoleDump('objects.getNoteText exception: ', ex);
			}
			return note;
		},
		isDefinedCell : function(row,col) {
			return col >= 0 && col < this.width && row >=0 && row < this.height && typeof(this.table[row][col]) != 'undefined';
		},
		isRealCell : function(row,col) {
			return this.isDefinedCell(row,col) && !this.table[row][col].isVirtual;
		},
		moveActiveCell : function(dir){
			var moving = true;
			var row = this.activePos[0];
			var col = this.activePos[1];
			//These are the positions that the active cell point to
			var activeRow = this.table[row][col][0];
			var activeCol = this.table[row][col][1];
			do {
				switch (dir) {
					case 'U'://up
						row --;
					break;
					case 'D': //down
						row ++;
					break;
					case 'L': //left
						col --;
					break;
					case 'R': //right
						col ++;
					break;
				}
			} while (this.isDefinedCell(row,col) && this.table[row][col].isVirtual
				&& this.table[row][col][0] == activeRow 
				&& this.table[row][col][1] == activeCol);
			
			if (this.isDefinedCell(row,col)) {
				this.activePos[0] = row;
				this.activePos[1] = col;
			}
			
			var c = document.getElementById('textbox-coords');
			if (c) {
				c.value = "(" + this.activePos[0] + "," + this.activePos[1]+")";
			}
		},
		getActiveCell : function() {
			var active = this.getNodeForCoord(this.activePos[0],this.activePos[1]);
			var content = document.getElementById("frame-content");
			if (content && active) {
				content.value = active.textContent;
			}
			content = document.getElementById("textbox-cell-id");
			if (content && active) {
				content.value = active.hasAttribute("id") ? cleanSpaces(active.getAttribute("id")) : "";
			}
	
			return active;
		},
		/**
		* Get all headers for a specific coordinate
		* @param row The row portion of the coordinate
		* @param col The column portion of the coordinate
		* @returns Object with the following arrays:
		*		rowHeaders : all of the row headers for the node
		*		colHeaders : all of the column headers for the node
		*		idHeaders : all of the ID headers for the node
		*/
		getHeadersForCoord : function(row,col) {
	    /* * First, search left from the cell's position to find row header cells. Then
			search upwards to find column header cells. The search in a given direction
			stops when the edge of the table is reached or when a data cell is found
			after a header cell.
	
	    * Row headers are inserted into the list in the order they appear in the table.
			For left-to-right tables, headers are inserted from left to right.
	
	    * Column headers are inserted after row headers, in the order they appear in
			the table, from top to bottom.
	
	    * If a header cell has the headers attribute set, then the headers referenced
			by this attribute are inserted into the list and the search stops for the
			current direction.
	
	    * TD cells that set the axis attribute are also treated as header cells.*/
	
			var r = new Object();
			r.rowHeaders = new Array();
			r.colHeaders = new Array();
			r.idHeaders = new Array();
			var node;
			var i;
			var activeNode = this.getNodeForCoord(row,col);
			if (activeNode && activeNode.tagName.toLowerCase() != 'th') {
	
				//Row headers
				for (i = 0; i < col; i ++) {
					node = this.getNodeForCoord(row,i);
					if (node && node.tagName.toLowerCase() == 'th' && cleanSpaces(node.textContent) != '') {
						r.rowHeaders[r.rowHeaders.length] = node;
					}
				}
	
				//Col headers
				for (i = 0; i < row; i ++) {
					node = this.getNodeForCoord(i,col);
					if (node && node.tagName.toLowerCase() == 'th' && cleanSpaces(node.textContent) != '') {
						r.colHeaders[r.colHeaders.length] = node;
					}
				}
			}
			
			//ID headers
			node = this.getNodeForCoord(row,col);
			var hnode;
			if (node && node.hasAttribute("headers")) {
				var ids = GetValueFromAttributes(node,['headers'],"").split(" ");
				for (i = 0; i < ids.length; i ++) {
					hnode = node.ownerDocument.getElementById(ids[i]);
					if (hnode  && cleanSpaces(hnode.textContent) != '') {
						r.idHeaders[r.idHeaders.length] = hnode;
					}
				}
			}
	
			return r;
		},
		getNodeForCoord : function(row,col){
			if (row < 0) {
				row = 0;
			} else if (row >= this.height) { 
				row = this.height - 1;
			}
			if (col < 0) {
				col = 0;
			} else if (col >= this.width) { 
				col = this.width - 1;
			}
			
			var a = this.table[row][col];
			if (typeof(a) != 'undefined') {
				return this.tableNode.rows[a[0]].cells[a[1]];
			}
			return null;
			
		},
		dataTableHasHeaders : function(node){
			var r;
			var c;
			var thColCount=0;
			var thRowCount = 0;
		   //contains data from tablenode.tHead.rows &  tablenode.tFoot.rows
			if (node.rows[0].cells.length > 1) {
				for (c=0; c < node.rows[0].cells.length; c++) {  // the first cell in each column has a header
					if (node.rows[0].cells[c].tagName.toLowerCase() == 'th' && cleanSpaces(node.rows[0].cells[c].textContent) != '') {thColCount++;}
				}
				if (node.rows[0].cells.length == thColCount) return true;
			}
			if (node.rows.length > 1){
				for (r=0; r < node.rows.length; r++) {
					if (node.rows[r].cells[0].tagName.toLowerCase() == 'th' && cleanSpaces(node.rows[r].cells[0].textContent) != '') {thRowCount++;}
				}
				if (node.rows.length == thRowCount) return true;
			}
			if (node.rows[0].cells.length-1 == thColCount && node.rows.length-1 == thRowCount) return true;
			return false;
		},
	
		parseNode : function(node) {
			//By here we already know that the table is a data table, thanks to ParseDocument
			//this.parseTableHelper(node);
			
			//Initalize the table
			this.tableNode = node;
			this.baseuri = getBaseURI(node);
			this.table = new Array(node.rows.length);
			if (node.caption && node.caption.textContent) {
				this.caption = cleanSpaces(node.caption.textContent);
			} else {
				this.caption = accessext_getBundleSingleString('none');
			}
			if (node.hasAttribute('summary')) {
				this.summary = node.getAttribute('summary');
				if (cleanSpaces(this.summary) == '') {this.missingsummary = true; }
			} else { 
				this.summary = accessext_getBundleSingleString('none');
				this.missingsummary = true;
			}
			var owner = this.tableNode.ownerDocument;
			var r;
			var c;
			var coffset;
			for (r = 0; r < node.rows.length; r ++) {
				this.table[r] = new Array();
			}
			
			var w,h;
			
			for (r = 0; r < node.rows.length; r ++) {
				coffset = 0;
				for (c = 0; c < node.rows[r].cells.length; c ++) {
					//If this cell has already been set (due to rowspan or colspan)
					while (typeof(this.table[r][coffset]) != 'undefined') {
						coffset++;
					}
					
					if (node.rows[r].cells[c].colSpan > 1 ||
						node.rows[r].cells[c].rowSpan > 1) {
						for (var i = 0; i < node.rows[r].cells[c].colSpan; i ++) {
							for (var j = 0; j < node.rows[r].cells[c].rowSpan; j ++) {
							//Create new rows if they have not been reached yet
								if (typeof(this.table[r + j])=='undefined') {
									this.table[r + j] = new Array();
								}
								this.table[r + j][coffset + i] = new Array(r,c);
								if (r + j > this.height) {
									this.height = r + j;
								}
								this.table[r + j][coffset + i].isVirtual = (i > 0 || j > 0);
							}
						}
					}
					else {
						this.table[r][coffset] = new Array(r,c);
					}
					coffset += node.rows[r].cells[c].colSpan;
				}
				if (coffset > this.width) {
					this.width = coffset;
				}
				if (r >= this.height) {
					this.height = r + 1;
				}
			}
			// determine if any headers are missing
	/*		
	 	for (r = 0; !this.missingheaders && r < node.rows.length; r ++) {
				for (c = 0; !this.missingheaders && c < node.rows[r].cells.length; c ++) {
					var targetNode = this.getNodeForCoord(r,c);
					if (targetNode.tagName.toLowerCase() == 'td') { 
						var headers = this.getHeadersForCoord(r,c);
						if (headers.rowHeaders.length == 0 && headers.colHeaders.length == 0){
							this.missingheaders = true;
						}
					}
				}
			} 
			*/
			this.missingheaders = !this.dataTableHasHeaders(node);
			if (accessext_isComplexDataTable(node)) {this.complexTableIssues(node);} //get the issues for complex tables
			//	setMaxSeverityForArray(loadArray); move this to where the array is 
			if (this.missingheaders || this.duplicatesummary || this.thmissingID || this.duplicateID || this.tdmissingheaders || this.invalidIDREF != null) {
				this.icita08 = accessext_getBundleSingleString('msg.fail');
			} else this.icita08 = accessext_getBundleSingleString('msg.pass');
			if (this.missingheaders) this.icita07 = accessext_getBundleSingleString('msg.fail'); else this.icita07 = accessext_getBundleSingleString('msg.pass');
	
			this.node = this.tableNode;
			
		},
		appendHeaderToNode : function (node,header,style) {
			var spanNode = node.ownerDocument.createElement("span");
			var textNode = node.ownerDocument.createTextNode(header.textContent);
			spanNode.appendChild(textNode);
			spanNode.setAttribute("style",style);
			spanNode.setAttribute("accessext-tableHeaderNode","true");
			if (node.childNodes.length) {
				node.insertBefore(spanNode,node.childNodes[0]);
			} else {
				node.appendChild(spanNode);
			}
		},
		populateCellsWithHeaders : function() {
			var r;
			var c;
			var h;
			var owner = this.tableNode.ownerDocument;
			for (r = 0; r < this.height ; r ++) {
				for (c = 0; c < this.width ; c++ ) {
					var a = this.table[r][c];
					if (a && !a.isVirtual) {
						var headers = this.getHeadersForCoord(r,c);
						var node = this.tableNode.rows[a[0]].cells[a[1]];
						for (h = 0; h < headers.rowHeaders.length; h ++) {
							this.appendHeaderToNode(node,headers.rowHeaders[h],"padding: 0.1em; background-color: red; color: white; border: black 1px solid;");
						}
						for (h = 0; h < headers.colHeaders.length; h ++) {
							this.appendHeaderToNode(node,headers.colHeaders[h],"padding: 0.1em; background-color: blue; color: white !important; border: black 1px solid;");
						}
						for (h = 0; h < headers.idHeaders.length; h ++) {
							this.appendHeaderToNode(node,headers.idHeaders[h],"padding: 0.1em; background-color: green; color: white; border: black 1px solid;");
						}
					}
				}
			}
		},
		removeCellHeaders : function (doc) {
			var xp = "//span[@accessext-tableHeaderNode='true']";
			var xpathResult = doc.evaluate(xp, doc,	null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
			var r = null;
			for (var i = 0 ; i < xpathResult.snapshotLength; i++ ) {
				r = xpathResult.snapshotItem(i);
				r.parentNode.removeChild(r);
			}
		},
		/* Debugging functions */
		/* Populate all table cells with text to identify them */
		populatePos : function(){
			var owner = this.tableNode.ownerDocument;
			var t = owner.createTextNode("Width: " + this.width + " height: "+ this.height);
			var div = owner.createElement("div");
			div.appendChild(t);
			this.tableNode.parentNode.insertBefore(div,this.tableNode);
			for (var r = 0; r < this.height; r ++) {
				for (var c = 0; c < this.width; c ++) {
					var a = this.table[r][c];
					if (a) {
						var text = owner.createTextNode( " (" + a.toString() + ")");
						var n = this.tableNode.rows[a[0]].cells[a[1]];
						n.appendChild(text);
					}
				}
			}
		},
		//This function assumes that accessext_isDataTable() is true.
		isComplexDataTable : function (node) {
			try {
				function isInteger(sText)	{
					var ValidChars = "0123456789";
					if (sText.length == 0) return false;
					for (i = 0; i < sText.length; i++) { 
						if (ValidChars.indexOf(sText.charAt(i)) == -1) return false;
					}
					return true;
				}		
				var r;
				var c;
				var allTH = 0;
			    if (node.tHead && node.tHead.rows.length > 1) return true; //more than one row in a thead element 
				for (r=0; r < node.rows.length; r++) { 
					var foundTD = false;
					var thCount = 0;				
					for (c=0; c < node.rows[r].cells.length; c++) {
						if (node.rows[r].cells[c].tagName.toLowerCase() == 'th') thCount++;
						var span = GetValueFromAttributes(node.rows[r].cells[c],['rowspan'],"");
						if (span != "" && isInteger(span) && span > 1) {return true;}
						span = GetValueFromAttributes(node.rows[r].cells[c],['colspan'],"");
						if (span != "" && isInteger(span) && span > 1) {return true;}				
						if (node.rows[r].cells[c].tagName.toLowerCase() == 'td') {
							foundTD = true;
							var headersStr = GetValueFromAttributes(node.rows[r].cells[c],['headers'],"");
							if (headersStr.length > 1) {
								var headers = headersStr.split(' ');
								if (headers.length > 2) {return true;}
							}
						}
					}
					if (thCount == node.rows[r].cells.length) {allTH++;}
					else if (foundTD && thCount > 1){return true;} //there is more than one th element in a row that also includes td elements
					if (allTH >= 2) {return true;} // there are two or more rows that contain only th elements in the table. 
				}
				return false;
			} catch (ex) {
				accessext_consoleDump('isComplexDataTable exception: ', ex);
			}
		},
		complexTableIssues : function (node) {
			try {
				var r;
				var c;
				var idarray = new Array();
				var notfoundarray = new Array();
			 	for (r=0; r < node.rows.length; r++) { 
					for (c=0; c < node.rows[r].cells.length; c++) {
						if (node.rows[r].cells[c].tagName.toLowerCase() == 'th') {
							var id = node.rows[r].cells[c].hasAttribute("id") ? cleanSpaces(node.rows[r].cells[c].getAttribute("id")) : ""; 
							if	(id == '') {this.thmissingID = true;} else {this.duplicateID = AddToArray(id, idarray)|| this.duplicateID;}
						}
						else if (node.rows[r].cells[c].tagName.toLowerCase() == 'td') {
							var headersStr = GetValueFromAttributes(node.rows[r].cells[c],['headers'],"");
							if (headersStr.length == 0) {this.tdmissingheaders = true;}
							else {
								var headers = headersStr.split(' ');
								for (i = 0, found = false; i < headers.length && !found; i++) { // this is probably going to be too time expensive
									for(var j=0;j < idarray.length && !found; j++) {if(headers[i]==idarray[j]) found = true;}
									if (!found) {AddToArray(headers[i], notfoundarray);}
								}
								if (!found) {this.invalidIDREF = notfoundarray.join(",");} 
							}
						}
					}
				}
				return false;
			} catch (ex) {
				accessext_consoleDump('complexTableIssues exception: ', ex);
			}
		}
	}
	function AddToArray(id, idarray){
		var isDuplicate=false;
		var k=0;
		while(k < idarray.length && idarray[k] != id) {k++;}
		if (k >= idarray.length) {idarray[idarray.length] = id;} else {isDuplicate = true;}
		return isDuplicate;
	}							
AccessibilityExtension.LIB.extend(AccessibilityExtension.LIB,
{	
	newDataTable: function(node, order) { 
		var n = new dataTable(node, order);
		return n; 
	},
});
}});
